--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
meshchatx/src/backend/database/schema.py 666c90875aa810547703b5d00d304e8dd1917a4f (666c9087) Text, 43.10 KB
Tff7b72from T7ee787.T7ee787provider Tff7b72import Te6edf3DatabaseProvider
Tff7b72class T56d364DatabaseSchemaTb4b4b4:
Te6edf3LATEST_VERSION Tff7b72= T79c0ff36
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3providerTb4b4b4: Te6edf3DatabaseProviderTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffprovider Tff7b72= Te6edf3provider
Tff7b72def Td2a8ff_safe_executeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3queryTb4b4b4, Te6edf3paramsTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
T8b949e# Silence expected errors during migrations (e.g. duplicate columns/indexes)
Te6edf3err_msg Tff7b72= Tffa657strTb4b4b4(Te6edf3eTb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72if Ta5d6ff"Ta5d6ffduplicate column nameTa5d6ff" Tff7b72in Te6edf3err_msg Tff7b72or Ta5d6ff"Ta5d6ffalready existsTa5d6ff" Tff7b72in Te6edf3err_msgTb4b4b4:
Tff7b72return Tff7b72None
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase operation failed: Tffd700{Te6edf3queryTb4b4b4[Tb4b4b4:T79c0ff100Tb4b4b4]Tffd700}Ta5d6ff... Error: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffinitializeTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# Create core tables if they don't exist
Tff7b72selfTff7b72.Td2a8ff_create_initial_tablesTb4b4b4(Tb4b4b4)
T8b949e# Run migrations
Te6edf3current_version Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_versionTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmigrateTb4b4b4(Te6edf3current_versionTb4b4b4)
Tff7b72def Td2a8ff_ensure_columnTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3table_nameTb4b4b4, Te6edf3column_nameTb4b4b4, Te6edf3column_typeTb4b4b4)Tb4b4b4:
T8b949e"""Add a column to a table if it doesn't exist."""
T8b949e# First check if it exists using PRAGMA
Te6edf3cursor Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffconnectionTff7b72.Td2a8ffcursorTb4b4b4(Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3cursorTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffPRAGMA table_info(Tffd700{Te6edf3table_nameTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)
Te6edf3columns Tff7b72= Tb4b4b4[Te6edf3rowTb4b4b4[T79c0ff1Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3cursorTff7b72.Td2a8fffetchallTb4b4b4(Tb4b4b4)Tb4b4b4]
Tff7b72finallyTb4b4b4:
Te6edf3cursorTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3column_name Tff7b72not Tff7b72in Te6edf3columnsTb4b4b4:
Tff7b72tryTb4b4b4:
T8b949e# SQLite has limitations on ALTER TABLE ADD COLUMN:
T8b949e# 1. Cannot add UNIQUE or PRIMARY KEY columns
T8b949e# 2. Cannot add columns with non-constant defaults (like CURRENT_TIMESTAMP)
T8b949e# Strip non-constant defaults if present for the ALTER TABLE statement
Te6edf3stmt_type Tff7b72= Te6edf3column_type
Te6edf3forbidden_defaults Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6ffCURRENT_TIMESTAMPTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffCURRENT_TIMETa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffCURRENT_DATETa5d6ff"Tb4b4b4,
Tb4b4b4]
Tff7b72for Te6edf3forbidden Tff7b72in Te6edf3forbidden_defaultsTb4b4b4:
Tff7b72if Ta5d6fffTa5d6ff"Ta5d6ffDEFAULT Tffd700{Te6edf3forbiddenTffd700}Ta5d6ff" Tff7b72in Te6edf3stmt_typeTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e# Remove the DEFAULT part for the ALTER statement
Tff7b72import T7ee787re
Te6edf3stmt_type Tff7b72= Te6edf3reTff7b72.Td2a8ffsubTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDEFAULTTffea00\\Ta5d6ffs+Tffd700{Te6edf3forbiddenTffd700}Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff"Tb4b4b4,
Te6edf3stmt_typeTb4b4b4,
Te6edf3flagsTff7b72=Te6edf3reTff7b72.Td2a8ffIGNORECASETb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
T8b949e# Use the connection directly to avoid any middle-ware issues
Te6edf3res Tff7b72= Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffALTER TABLE Tffd700{Te6edf3table_nameTffd700}Ta5d6ff ADD COLUMN Tffd700{Te6edf3column_nameTffd700}Ta5d6ff Tffd700{Te6edf3stmt_typeTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3res Tff7b72is Tff7b72not Tff7b72None
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
T8b949e# Log but don't crash, we might be able to continue
Tffa657printTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffUnexpected error adding column Tffd700{Te6edf3column_nameTffd700}Ta5d6ff to Tffd700{Te6edf3table_nameTffd700}Ta5d6ff: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72return Tff7b72True
Tff7b72return Tff7b72True
Tff7b72def Td2a8ff_sync_table_columnsTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3table_nameTb4b4b4, Te6edf3create_sqlTb4b4b4)Tb4b4b4:
T8b949e"""Parses a CREATE TABLE statement and ensures all columns exist in the actual table.
This is a robust way to handle legacy tables that are missing columns.
"""
T8b949e# Find the first '(' and the last ')'
Te6edf3start_idx Tff7b72= Te6edf3create_sqlTff7b72.Td2a8fffindTb4b4b4(Ta5d6ff"Ta5d6ff(Ta5d6ff"Tb4b4b4)
Te6edf3end_idx Tff7b72= Te6edf3create_sqlTff7b72.Td2a8ffrfindTb4b4b4(Ta5d6ff"Ta5d6ff)Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3start_idx Tff7b72== Tff7b72-T79c0ff1 Tff7b72or Te6edf3end_idx Tff7b72== Tff7b72-T79c0ff1Tb4b4b4:
Tff7b72return
Te6edf3inner_content Tff7b72= Te6edf3create_sqlTb4b4b4[Te6edf3start_idx Tff7b72+ T79c0ff1 Tb4b4b4: Te6edf3end_idxTb4b4b4]
T8b949e# Split by comma but ignore commas inside parentheses (e.g. DECIMAL(10,2))
Te6edf3definitions Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3depth Tff7b72= T79c0ff0
Te6edf3current Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72for Te6edf3char Tff7b72in Te6edf3inner_contentTb4b4b4:
Tff7b72if Te6edf3char Tff7b72== Ta5d6ff"Ta5d6ff(Ta5d6ff"Tb4b4b4:
Te6edf3depth Tff7b72+Tff7b72= T79c0ff1
Tff7b72elif Te6edf3char Tff7b72== Ta5d6ff"Ta5d6ff)Ta5d6ff"Tb4b4b4:
Te6edf3depth Tff7b72-Tff7b72= T79c0ff1
Tff7b72if Te6edf3char Tff7b72== Ta5d6ff"Ta5d6ff,Ta5d6ff" Tff7b72and Te6edf3depth Tff7b72== T79c0ff0Tb4b4b4:
Te6edf3definitionsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3currentTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3current Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3current Tff7b72+Tff7b72= Te6edf3char
Tff7b72if Te6edf3currentTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3definitionsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3currentTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3definition Tff7b72in Te6edf3definitionsTb4b4b4:
Te6edf3definition Tff7b72= Te6edf3definitionTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
T8b949e# Skip table-level constraints
Tff7b72if Tff7b72not Te6edf3definition Tff7b72or Te6edf3definitionTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstartswithTb4b4b4(
Tb4b4b4(Ta5d6ff"Ta5d6ffPRIMARY KEYTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffFOREIGN KEYTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffUNIQUETa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCHECKTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3parts Tff7b72= Te6edf3definitionTff7b72.Td2a8ffsplitTb4b4b4(Tff7b72NoneTb4b4b4, T79c0ff1Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3partsTb4b4b4:
Tff7b72continue
Te6edf3column_name Tff7b72= Te6edf3partsTb4b4b4[T79c0ff0Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff'Ta5d6ff"Ta5d6ff'Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff"Ta5d6ff`Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff"Ta5d6ff[Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)
Te6edf3column_type Tff7b72= Te6edf3partsTb4b4b4[T79c0ff1Tb4b4b4] Tff7b72if Tffa657lenTb4b4b4(Te6edf3partsTb4b4b4) Tff7b72> T79c0ff1 Tff7b72else Ta5d6ff"Ta5d6ffTEXTTa5d6ff"
T8b949e# Special case for column types that are already PRIMARY KEY
Tff7b72if Ta5d6ff"Ta5d6ffPRIMARY KEYTa5d6ff" Tff7b72in Te6edf3column_typeTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4) Tff7b72and Te6edf3column_nameTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4) Tff7b72!= Ta5d6ff"Ta5d6ffIDTa5d6ff"Tb4b4b4:
T8b949e# We usually don't want to ALTER TABLE ADD COLUMN with PRIMARY KEY
T8b949e# unless it's the main ID which should already exist
Tff7b72continue
Tff7b72selfTff7b72.Td2a8ff_ensure_columnTb4b4b4(Te6edf3table_nameTb4b4b4, Te6edf3column_nameTb4b4b4, Te6edf3column_typeTb4b4b4)
Tff7b72def Td2a8ff_get_current_versionTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3row Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchoneTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT value FROM config WHERE key = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffdatabase_versionTa5d6ff"Tb4b4b4,Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3rowTb4b4b4:
Tff7b72return Tffa657intTb4b4b4(Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffvalueTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to get database version: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return T79c0ff0
Tff7b72def Td2a8ff_create_initial_tablesTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e# We create the config table first so we can track version
Te6edf3config_sql Tff7b72= Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS config (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff key TEXT UNIQUE,
Ta5d6ff value TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Te6edf3config_sqlTb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_sync_table_columnsTb4b4b4(Ta5d6ff"Ta5d6ffconfigTa5d6ff"Tb4b4b4, Te6edf3config_sqlTb4b4b4)
T8b949e# Other essential tables that were present from version 1
T8b949e# Peewee automatically creates tables if they don't exist.
T8b949e# Here we define the full schema for all tables as they should be now.
Te6edf3tables Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS announces (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff aspect TEXT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff identity_public_key TEXT,
Ta5d6ff app_data TEXT,
Ta5d6ff rssi INTEGER,
Ta5d6ff snr REAL,
Ta5d6ff quality REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffcustom_destination_display_namesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS custom_destination_display_names (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff display_name TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fffavourite_destinationsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS favourite_destinations (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff display_name TEXT,
Ta5d6ff aspect TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_messagesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_messages (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff hash TEXT UNIQUE,
Ta5d6ff source_hash TEXT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff peer_hash TEXT,
Ta5d6ff state TEXT,
Ta5d6ff progress REAL,
Ta5d6ff is_incoming INTEGER,
Ta5d6ff method TEXT,
Ta5d6ff delivery_attempts INTEGER DEFAULT 0,
Ta5d6ff next_delivery_attempt_at REAL,
Ta5d6ff title TEXT,
Ta5d6ff content TEXT,
Ta5d6ff fields TEXT,
Ta5d6ff timestamp REAL,
Ta5d6ff rssi INTEGER,
Ta5d6ff snr REAL,
Ta5d6ff quality REAL,
Ta5d6ff is_spam INTEGER DEFAULT 0,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_conversation_read_stateTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_conversation_read_state (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff last_read_at DATETIME,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_user_iconsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_user_icons (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff icon_name TEXT,
Ta5d6ff foreground_colour TEXT,
Ta5d6ff background_colour TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffblocked_destinationsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS blocked_destinations (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffspam_keywordsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS spam_keywords (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff keyword TEXT UNIQUE,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffarchived_pagesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS archived_pages (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff page_path TEXT,
Ta5d6ff content TEXT,
Ta5d6ff hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffcrawl_tasksTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS crawl_tasks (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff page_path TEXT,
Ta5d6ff retry_count INTEGER DEFAULT 0,
Ta5d6ff last_retry_at DATETIME,
Ta5d6ff next_retry_at DATETIME,
Ta5d6ff status TEXT DEFAULT Ta5d6ff'Ta5d6ffpendingTa5d6ff'Ta5d6ff,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff UNIQUE(destination_hash, page_path)
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_forwarding_rulesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_forwarding_rules (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff forward_to_hash TEXT,
Ta5d6ff source_filter_hash TEXT,
Ta5d6ff is_active INTEGER DEFAULT 1,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_forwarding_mappingsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_forwarding_mappings (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff alias_identity_private_key TEXT,
Ta5d6ff alias_hash TEXT UNIQUE,
Ta5d6ff original_sender_hash TEXT,
Ta5d6ff final_recipient_hash TEXT,
Ta5d6ff original_destination_hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffcall_historyTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS call_history (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff remote_identity_hash TEXT,
Ta5d6ff remote_identity_name TEXT,
Ta5d6ff is_incoming INTEGER,
Ta5d6ff status TEXT,
Ta5d6ff duration_seconds INTEGER,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffvoicemailsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS voicemails (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff remote_identity_hash TEXT,
Ta5d6ff remote_identity_name TEXT,
Ta5d6ff filename TEXT,
Ta5d6ff duration_seconds INTEGER,
Ta5d6ff is_read INTEGER DEFAULT 0,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffnotification_viewed_stateTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS notification_viewed_state (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff last_viewed_at DATETIME,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_telemetryTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_telemetry (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff timestamp REAL,
Ta5d6ff data BLOB,
Ta5d6ff received_from TEXT,
Ta5d6ff physical_link TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff UNIQUE(destination_hash, timestamp)
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffringtonesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS ringtones (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff filename TEXT,
Ta5d6ff display_name TEXT,
Ta5d6ff storage_filename TEXT,
Ta5d6ff is_primary INTEGER DEFAULT 0,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffcontactsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS contacts (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT,
Ta5d6ff remote_identity_hash TEXT UNIQUE,
Ta5d6ff lxmf_address TEXT,
Ta5d6ff lxst_address TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffnotificationsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS notifications (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff type TEXT,
Ta5d6ff remote_hash TEXT,
Ta5d6ff title TEXT,
Ta5d6ff content TEXT,
Ta5d6ff is_viewed INTEGER DEFAULT 0,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffkeyboard_shortcutsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS keyboard_shortcuts (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff action TEXT,
Ta5d6ff keys TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff UNIQUE(identity_hash, action)
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffmap_drawingsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS map_drawings (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff name TEXT,
Ta5d6ff data TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_last_sent_icon_hashesTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_last_sent_icon_hashes (
Ta5d6ff destination_hash TEXT PRIMARY KEY,
Ta5d6ff icon_hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6ffdebug_logsTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS debug_logs (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff timestamp REAL,
Ta5d6ff level TEXT,
Ta5d6ff module TEXT,
Ta5d6ff message TEXT,
Ta5d6ff is_anomaly INTEGER DEFAULT 0,
Ta5d6ff anomaly_type TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_foldersTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_folders (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT UNIQUE,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_conversation_foldersTa5d6ff"Tb4b4b4: Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_conversation_folders (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff peer_hash TEXT UNIQUE,
Ta5d6ff folder_id INTEGER,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff FOREIGN KEY (folder_id) REFERENCES lxmf_folders(id) ON DELETE CASCADE
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Tb4b4b4}
Tff7b72for Te6edf3table_nameTb4b4b4, Te6edf3create_sql Tff7b72in Te6edf3tablesTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Te6edf3create_sqlTb4b4b4)
T8b949e# Robust self-healing: Ensure existing tables have all modern columns
Tff7b72selfTff7b72.Td2a8ff_sync_table_columnsTb4b4b4(Te6edf3table_nameTb4b4b4, Te6edf3create_sqlTb4b4b4)
T8b949e# Create indexes that were present
Tff7b72if Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_aspect ON announces(aspect)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_identity_hash ON announces(identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_updated_at ON announces(updated_at)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6fflxmf_messagesTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_source_hash ON lxmf_messages(source_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_destination_hash ON lxmf_messages(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_peer_hash ON lxmf_messages(peer_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_timestamp ON lxmf_messages(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_peer_ts ON lxmf_messages(peer_hash, timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6ffblocked_destinationsTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_blocked_destinations_hash ON blocked_destinations(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6ffspam_keywordsTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_spam_keywords_keyword ON spam_keywords(keyword)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6ffnotification_viewed_stateTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_notification_viewed_state_destination_hash ON notification_viewed_state(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_notification_viewed_state_dest_hash_unique ON notification_viewed_state(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6fflxmf_telemetryTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_telemetry_destination_hash ON lxmf_telemetry(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_telemetry_timestamp ON lxmf_telemetry(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_lxmf_telemetry_dest_ts_unique ON lxmf_telemetry(destination_hash, timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elif Te6edf3table_name Tff7b72== Ta5d6ff"Ta5d6ffdebug_logsTa5d6ff"Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_timestamp ON debug_logs(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_level ON debug_logs(level)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_anomaly ON debug_logs(is_anomaly)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffmigrateTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3current_versionTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff7Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS archived_pages (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff page_path TEXT,
Ta5d6ff content TEXT,
Ta5d6ff hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_archived_pages_destination_hash ON archived_pages(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_archived_pages_page_path ON archived_pages(page_path)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_archived_pages_hash ON archived_pages(hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff8Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS crawl_tasks (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff page_path TEXT,
Ta5d6ff retry_count INTEGER DEFAULT 0,
Ta5d6ff last_retry_at DATETIME,
Ta5d6ff next_retry_at DATETIME,
Ta5d6ff status TEXT DEFAULT Ta5d6ff'Ta5d6ffpendingTa5d6ff'Ta5d6ff,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_crawl_tasks_destination_hash ON crawl_tasks(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_crawl_tasks_page_path ON crawl_tasks(page_path)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff9Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_forwarding_rules (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff forward_to_hash TEXT,
Ta5d6ff source_filter_hash TEXT,
Ta5d6ff is_active INTEGER DEFAULT 1,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_forwarding_rules_identity_hash ON lxmf_forwarding_rules(identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_forwarding_mappings (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff alias_identity_private_key TEXT,
Ta5d6ff alias_hash TEXT UNIQUE,
Ta5d6ff original_sender_hash TEXT,
Ta5d6ff final_recipient_hash TEXT,
Ta5d6ff original_destination_hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_forwarding_mappings_alias_hash ON lxmf_forwarding_mappings(alias_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_forwarding_mappings_sender_hash ON lxmf_forwarding_mappings(original_sender_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_forwarding_mappings_recipient_hash ON lxmf_forwarding_mappings(final_recipient_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff10Tb4b4b4:
T8b949e# Ensure unique constraints exist for ON CONFLICT clauses
T8b949e# SQLite doesn't support adding UNIQUE constraints via ALTER TABLE,
T8b949e# but a UNIQUE index works for ON CONFLICT.
T8b949e# Clean up duplicates before adding unique indexes
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM announces WHERE id NOT IN (SELECT MAX(id) FROM announces GROUP BY destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM crawl_tasks WHERE id NOT IN (SELECT MAX(id) FROM crawl_tasks GROUP BY destination_hash, page_path)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM custom_destination_display_names WHERE id NOT IN (SELECT MAX(id) FROM custom_destination_display_names GROUP BY destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM favourite_destinations WHERE id NOT IN (SELECT MAX(id) FROM favourite_destinations GROUP BY destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_user_icons WHERE id NOT IN (SELECT MAX(id) FROM lxmf_user_icons GROUP BY destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_read_state WHERE id NOT IN (SELECT MAX(id) FROM lxmf_conversation_read_state GROUP BY destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE id NOT IN (SELECT MAX(id) FROM lxmf_messages GROUP BY hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_announces_destination_hash_unique ON announces(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_crawl_tasks_destination_path_unique ON crawl_tasks(destination_hash, page_path)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_custom_display_names_dest_hash_unique ON custom_destination_display_names(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_favourite_destinations_dest_hash_unique ON favourite_destinations(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_lxmf_messages_hash_unique ON lxmf_messages(hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_lxmf_user_icons_dest_hash_unique ON lxmf_user_icons(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_lxmf_conversation_read_state_dest_hash_unique ON lxmf_conversation_read_state(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff11Tb4b4b4:
T8b949e# Add is_spam column to lxmf_messages if it doesn't exist
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE lxmf_messages ADD COLUMN is_spam INTEGER DEFAULT 0Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff12Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS call_history (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff remote_identity_hash TEXT,
Ta5d6ff remote_identity_name TEXT,
Ta5d6ff is_incoming INTEGER,
Ta5d6ff status TEXT,
Ta5d6ff duration_seconds INTEGER,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_call_history_remote_hash ON call_history(remote_identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_call_history_timestamp ON call_history(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff13Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS voicemails (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff remote_identity_hash TEXT,
Ta5d6ff remote_identity_name TEXT,
Ta5d6ff filename TEXT,
Ta5d6ff duration_seconds INTEGER,
Ta5d6ff is_read INTEGER DEFAULT 0,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_voicemails_remote_hash ON voicemails(remote_identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_voicemails_timestamp ON voicemails(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff14Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS notification_viewed_state (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT UNIQUE,
Ta5d6ff last_viewed_at DATETIME,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_notification_viewed_state_destination_hash ON notification_viewed_state(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_notification_viewed_state_dest_hash_unique ON notification_viewed_state(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff15Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_telemetry (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff destination_hash TEXT,
Ta5d6ff timestamp REAL,
Ta5d6ff data BLOB,
Ta5d6ff received_from TEXT,
Ta5d6ff physical_link TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff UNIQUE(destination_hash, timestamp)
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_telemetry_destination_hash ON lxmf_telemetry(destination_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_telemetry_timestamp ON lxmf_telemetry(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE UNIQUE INDEX IF NOT EXISTS idx_lxmf_telemetry_dest_ts_unique ON lxmf_telemetry(destination_hash, timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff16Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE lxmf_forwarding_rules ADD COLUMN name TEXTTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff17Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS ringtones (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff filename TEXT,
Ta5d6ff display_name TEXT,
Ta5d6ff storage_filename TEXT,
Ta5d6ff is_primary INTEGER DEFAULT 0,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff18Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS contacts (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT,
Ta5d6ff remote_identity_hash TEXT UNIQUE,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_contacts_remote_identity_hash ON contacts(remote_identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff19Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_call_history_remote_name ON call_history(remote_identity_name)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff20Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS notifications (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff type TEXT,
Ta5d6ff remote_hash TEXT,
Ta5d6ff title TEXT,
Ta5d6ff content TEXT,
Ta5d6ff is_viewed INTEGER DEFAULT 0,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_notifications_remote_hash ON notifications(remote_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_notifications_timestamp ON notifications(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff21Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS keyboard_shortcuts (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff identity_hash TEXT,
Ta5d6ff action TEXT,
Ta5d6ff keys TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff UNIQUE(identity_hash, action)
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_keyboard_shortcuts_identity_hash ON keyboard_shortcuts(identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff22Tb4b4b4:
T8b949e# Optimize fetching conversations and favorites
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_timestamp ON lxmf_messages(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_favourite_destinations_aspect ON favourite_destinations(aspect)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e# Add index for faster searching in announces
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_updated_at ON announces(updated_at)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff23Tb4b4b4:
T8b949e# Further optimize conversation fetching
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_conv_optim ON lxmf_messages(source_hash, destination_hash, timestamp DESC)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e# Add index for unread message filtering
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_state_incoming ON lxmf_messages(state, is_incoming)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_aspect ON announces(aspect)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff24Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS call_recordings (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff remote_identity_hash TEXT,
Ta5d6ff remote_identity_name TEXT,
Ta5d6ff filename_rx TEXT,
Ta5d6ff filename_tx TEXT,
Ta5d6ff duration_seconds INTEGER,
Ta5d6ff timestamp REAL,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_call_recordings_remote_hash ON call_recordings(remote_identity_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_call_recordings_timestamp ON call_recordings(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff25Tb4b4b4:
T8b949e# Add docs_downloaded to config if not exists
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT OR IGNORE INTO config (key, value) VALUES (?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffdocs_downloadedTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff0Ta5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff26Tb4b4b4:
T8b949e# Add initial_docs_download_attempted to config if not exists
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT OR IGNORE INTO config (key, value) VALUES (?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffinitial_docs_download_attemptedTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff0Ta5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff28Tb4b4b4:
T8b949e# Add preferred_ringtone_id to contacts
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE contacts ADD COLUMN preferred_ringtone_id INTEGER DEFAULT NULLTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff29Tb4b4b4:
T8b949e# Performance optimization indexes
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_peer_hash ON lxmf_messages(peer_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_timestamp ON lxmf_messages(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_messages_peer_ts ON lxmf_messages(peer_hash, timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_announces_updated_at ON announces(updated_at)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff30Tb4b4b4:
T8b949e# Add custom_image to contacts
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE contacts ADD COLUMN custom_image TEXT DEFAULT NULLTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff31Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_last_sent_icon_hashes (
Ta5d6ff destination_hash TEXT PRIMARY KEY,
Ta5d6ff icon_hash TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff32Tb4b4b4:
T8b949e# Add tutorial_seen and changelog_seen_version to config
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT OR IGNORE INTO config (key, value) VALUES (?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6fftutorial_seenTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffalseTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffINSERT OR IGNORE INTO config (key, value) VALUES (?, ?)Ta5d6ff"Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffchangelog_seen_versionTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff0.0.0Ta5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff33Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS debug_logs (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff timestamp REAL,
Ta5d6ff level TEXT,
Ta5d6ff module TEXT,
Ta5d6ff message TEXT,
Ta5d6ff is_anomaly INTEGER DEFAULT 0,
Ta5d6ff anomaly_type TEXT,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_timestamp ON debug_logs(timestamp)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_level ON debug_logs(level)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_debug_logs_anomaly ON debug_logs(is_anomaly)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff34Tb4b4b4:
T8b949e# Add updated_at to crawl_tasks
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE crawl_tasks ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMPTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff35Tb4b4b4:
T8b949e# Add lxmf_address and lxst_address to contacts
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE contacts ADD COLUMN lxmf_address TEXT DEFAULT NULLTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffALTER TABLE contacts ADD COLUMN lxst_address TEXT DEFAULT NULLTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3current_version Tff7b72< T79c0ff36Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_folders (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff name TEXT UNIQUE,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(Ta5d6ff"""
Ta5d6ff CREATE TABLE IF NOT EXISTS lxmf_conversation_folders (
Ta5d6ff id INTEGER PRIMARY KEY AUTOINCREMENT,
Ta5d6ff peer_hash TEXT UNIQUE,
Ta5d6ff folder_id INTEGER,
Ta5d6ff created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
Ta5d6ff FOREIGN KEY (folder_id) REFERENCES lxmf_folders(id) ON DELETE CASCADE
Ta5d6ff )
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_conversation_folders_peer_hash ON lxmf_conversation_folders(peer_hash)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
Ta5d6ff"Ta5d6ffCREATE INDEX IF NOT EXISTS idx_lxmf_conversation_folders_folder_id ON lxmf_conversation_folders(folder_id)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e# Update version in config
Tff7b72selfTff7b72.Td2a8ff_safe_executeTb4b4b4(
T8b949e"""
INSERT INTO config (key, value, created_at, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT(key) DO UPDATE SET
value = EXCLUDED.value,
updated_at = EXCLUDED.updated_at
"""Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffdatabase_versionTa5d6ff"Tb4b4b4, Tffa657strTb4b4b4(Tff7b72selfTff7b72.Td2a8ffLATEST_VERSIONTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────